/*
 Copyright (c) 2012 Robert Foss, Roman Truba

 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 documentation files (the "Software"), to deal in the Software without restriction, including without limitation
 the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 permit persons to whom the Software is furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all copies or substantial
 portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
 THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package ru.truba.touchgrallery.TouchView;


import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.Image;
import ohos.agp.components.ScaleInfo;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Point;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.image.PixelMap;
import ohos.multimodalinput.event.TouchEvent;


import java.lang.ref.WeakReference;
import java.util.Timer;
import java.util.TimerTask;

import static ohos.agp.render.BlendMode.CLEAR;

//@SuppressLint("NewApi")
public class TouchImageView extends Image implements Component.DrawTask, Component.EstimateSizeListener, Component.TouchEventListener, Component.DoubleClickedListener {
    public static HiLogLabel hiLogLabel = new HiLogLabel(HiLog.LOG_APP, 1023, "tttttt");
//    private int positionForTouchImageView = -1;

    // private static final String TAG = "Touch";
    // These matrices will be used to move and zoom image
    Matrix matrix = new Matrix();
    Matrix savedMatrix = new Matrix();

    static final long DOUBLE_PRESS_INTERVAL = 600;
    static final float FRICTION = 0.9f;

    // We can be in one of these 4 states
    static final int NONE = 0;
    static final int DRAG = 1;
    static final int ZOOM = 2;
    static final int CLICK = 10;
    int mode = NONE;

    float redundantXSpace, redundantYSpace;
    float right, bottom, origWidth, origHeight, bmWidth, bmHeight;
    float width, height;


    Point last = new Point();
    Point mid = new Point();
    Point start = new Point();
    float[] m;
    float matrixX, matrixY;

    float saveScale = 1f;
    float minScale = 1f;
    float maxScale = 3f;
    float oldDist = 1f;

    Point lastDelta = new Point(0, 0);
    float velocity = 0;

    long lastPressTime = 0, lastDragTime = 0;
    boolean allowInert = false;

//    private Context mContext;
    private Timer mClickTimer;
    private ClickedListener mOnClickListener;
    private Object mScaleDetector;
    private EventHandler mTimerHandler = null;

    // Scale mode on DoubleTap
    private boolean zoomToOriginalSize = false;

    public boolean isZoomToOriginalSize() {
        return this.zoomToOriginalSize;
    }

    public void setZoomToOriginalSize(boolean zoomToOriginalSize) {
        this.zoomToOriginalSize = zoomToOriginalSize;
    }

    public boolean onLeftSide = false, onTopSide = false, onRightSide = false, onBottomSide = false;

    @Override
    public void setClickedListener(ClickedListener listener) {
        mOnClickListener = listener;
    }

    public TouchImageView(Context context) {
        super(context);
        super.setClickable(true);
        this.mContext = context;

        init();
    }

    public TouchImageView(Context context, AttrSet attrs) {
        super(context, attrs);
       super.setClickable(true);
        this.mContext = context;

        init();
    }

    protected void init() {
        addDrawTask(this);
        setEstimateSizeListener(this);
        mTimerHandler = new TimeHandler(this, EventRunner.getMainEventRunner());
        matrix.setTranslate(1f, 1f);
        m = new float[9];
        //   setTouchEventListener(this);
        //setDraggedListener();
       // setScaledListener(new ScaleListenner());

       setTouchEventListener(this);

       // setDoubleClickedListener(this);
      //  setTouchEventListener(this);
    }

    public void resetScale() {
        fillMatrixXY();
        matrix.postScale(minScale / saveScale, minScale / saveScale, width / 2, height / 2);
        saveScale = minScale;

        calcPadding();
        checkAndSetTranslate(0, 0);

        scaleMatrixToBounds();

        //setImageMatrix(matrix);
        invalidate();
    }

    public boolean pagerCanScroll() {
        if (mode != NONE) { return false; }
        return saveScale == minScale;
    }


    private void checkAndSetTranslate(float deltaX, float deltaY) {
        float scaleWidth = Math.round(origWidth * saveScale);
        float scaleHeight = Math.round(origHeight * saveScale);
        fillMatrixXY();
        if (scaleWidth < width) {
            deltaX = 0;
            if (matrixY + deltaY > 0)
            { deltaY = -matrixY; }
            else if (matrixY + deltaY < -bottom)
            { deltaY = -(matrixY + bottom); }
        } else if (scaleHeight < height) {
            deltaY = 0;
            if (matrixX + deltaX > 0)
            { deltaX = -matrixX; }
            else if (matrixX + deltaX < -right)
            { deltaX = -(matrixX + right); }
        } else {
            if (matrixX + deltaX > 0)
            { deltaX = -matrixX; }
            else if (matrixX + deltaX < -right)
            { deltaX = -(matrixX + right); }

            if (matrixY + deltaY > 0)
            { deltaY = -matrixY; }
            else if (matrixY + deltaY < -bottom)
            { deltaY = -(matrixY + bottom); }
        }
        matrix.postTranslate(deltaX, deltaY);
        checkSiding();
    }

    private void checkSiding() {
        fillMatrixXY();
        //Log.d(TAG, "x: " + matrixX + " y: " + matrixY + " left: " + right / 2 + " top:" + bottom / 2);
        float scaleWidth = Math.round(origWidth * saveScale);
        float scaleHeight = Math.round(origHeight * saveScale);
        onLeftSide = onRightSide = onTopSide = onBottomSide = false;
        if (-matrixX < 10.0f) { onLeftSide = true; }
        //Log.d("GalleryViewPager", String.format("ScaleW: %f; W: %f, MatrixX: %f", scaleWidth, width, matrixX));
        if ((scaleWidth >= width && (matrixX + scaleWidth - width) < 10) ||
                (scaleWidth <= width && -matrixX + scaleWidth <= width)) { onRightSide = true; }
        if (-matrixY < 10.0f) { onTopSide = true; }
        if (Math.abs(-matrixY + height - scaleHeight) < 10.0f) { onBottomSide = true; }
    }

    private void calcPadding() {
        right = width * saveScale - width - (2 * redundantXSpace * saveScale);
        bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
    }

    private void fillMatrixXY() {
      /*  matrix.getValues(m);
        matrixX = m[Matrix.MTRANS_X];
        matrixY = m[Matrix.MTRANS_Y];*/
        matrixX = matrix.getTranslateX();
        matrixY = matrix.getTranslateY();
    }

    private void scaleMatrixToBounds() {
        if (Math.abs(matrixX + right / 2) > 0.5f)
        { matrix.postTranslate(-(matrixX + right / 2), 0); }
        if (Math.abs(matrixY + bottom / 2) > 0.5f)
        { matrix.postTranslate(0, -(matrixY + bottom / 2)); }
    }


    @Override
    public void setPixelMap(PixelMap pixelMap) {
        super.setPixelMap(pixelMap);
        bmWidth = pixelMap.getImageInfo().size.width;
        bmHeight = pixelMap.getImageInfo().size.height;
    }


    private double distanceBetween(Point left, Point right) {
        return Math.sqrt(Math.pow(left.getPointX() - right.getPointX(), 2) + Math.pow(left.getPointY() - right.getPointY(), 2));
    }

    /**
     * Determine the space between the first two fingers
     */
    private float spacing(WrapMotionEvent event) {
        // ...
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    /**
     * Calculate the mid point of the first two fingers
     */
    private void midPoint(Point point, WrapMotionEvent event) {
        // ...
        float x = event.getX(0) + event.getX(1);
        float y = event.getY(0) + event.getY(1);
        point.modify(x / 2, y / 2);
    }

    private Point midPointF(WrapMotionEvent event) {
        // ...
        float x = event.getX(0) + event.getX(1);
        float y = event.getY(0) + event.getY(1);
        return new Point(x / 2, y / 2);
    }

/*    @Override
    public void setOnClickListener(OnClickListener l) {
        mOnClickListener = l;
    }*/

    @Override
    public void onDraw(Component component, Canvas canvas) {
        PixelMap pixelMap = getPixelMap();
        PixelMap newPixMap = null;
        if (pixelMap != null) {
            newPixMap = PixelMap.create(pixelMap, null);
            //    canvas.setMatrix(matrix);
            canvas.drawColor(Color.TRANSPARENT.getValue(), CLEAR);
            canvas.setMatrix(matrix);
            canvas.drawPixelMapHolder(new PixelMapHolder(newPixMap), 0, 0, new Paint());
        }
        if (!allowInert) { return; }
        final float deltaX = lastDelta.getPointX() * velocity, deltaY = lastDelta.getPointY() * velocity;
        if (deltaX > width || deltaY > height)
        {
            return;
        }
        velocity *= FRICTION;
        if (Math.abs(deltaX) < 0.1 && Math.abs(deltaY) < 0.1) { return; }
        checkAndSetTranslate(deltaX, deltaY);
        invalidate();
         /*   System.out.print("sasfsaf" + matrix.getScaleX());
            //if (!allowInert) return;
            final float deltaX = lastDelta.getPointX() * velocity, deltaY = lastDelta.getPointY() * velocity;
            if (deltaX > width || deltaY > height) {
                return;
            }
            velocity *= FRICTION;
            if (Math.abs(deltaX) < 0.1 && Math.abs(deltaY) < 0.1) return;
            checkAndSetTranslate(deltaX, deltaY);*/
    }

    @Override
    public boolean onEstimateSize(int i, int i1) {
        width = MeasureSpec.getSize(i);
        height = MeasureSpec.getSize(i1);
        //Fit to screen.
        float scale;
        float scaleX = width / bmWidth;
        float scaleY = height / bmHeight;
        scale = Math.min(scaleX, scaleY);
        matrix.setScale(scale, scale);
        //minScale = scale;
        //   setImageMatrix(matrix);

        saveScale = 1f;

        // Center the image
        redundantYSpace = height - (scale * bmHeight);
        redundantXSpace = width - (scale * bmWidth);
        redundantYSpace /= (float) 2;
        redundantXSpace /= (float) 2;

        matrix.postTranslate(redundantXSpace, redundantYSpace);

        origWidth = width - 2 * redundantXSpace;
        origHeight = height - 2 * redundantYSpace;
        calcPadding();
        // setImageMatrix(matrix);
        invalidate();
        return false;
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent rawEvent) {
        WrapMotionEvent event = WrapMotionEvent.wrap(rawEvent);
     /*   if (mScaleDetector != null)
        {
            ((ScaleListenner)mScaleDetector);
        }*/
        fillMatrixXY();
        Point curr = new Point(event.getX(), event.getY());

        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                allowInert = false;
                // savedMatrix.set(matrix);
                savedMatrix.setMatrix(matrix);
                last.modify(event.getX(), event.getY());
                start.modify(last);
                mode = DRAG;

                break;
            case TouchEvent.OTHER_POINT_DOWN:
                oldDist = spacing(event);
                //Log.d(TAG, "oldDist=" + oldDist);
                if (oldDist > 10f) {
                    savedMatrix.setMatrix(matrix);
                    midPoint(mid, event);
                    mode = ZOOM;
                    //Log.d(TAG, "mode=ZOOM");
                }
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                allowInert = true;
                mode = NONE;
                int xDiff = (int) Math.abs(event.getX() - start.getPointX());
                int yDiff = (int) Math.abs(event.getY() - start.getPointY());

                if (xDiff < CLICK && yDiff < CLICK) {


                    //Perform scale on double click
                    long pressTime = System.currentTimeMillis();
                    if (pressTime - lastPressTime <= DOUBLE_PRESS_INTERVAL) {
                        if (mClickTimer != null) { mClickTimer.cancel(); }
                        if (saveScale == 1) {
                            final float targetScale = maxScale / saveScale;
                            matrix.postScale(targetScale, targetScale, start.getPointX(), start.getPointY());
                            saveScale = maxScale;
                        } else {
                            matrix.postScale(minScale / saveScale, minScale / saveScale, width / 2, height / 2);
                            saveScale = minScale;
                        }
                        calcPadding();
                        checkAndSetTranslate(0, 0);
                        lastPressTime = 0;
                    }
                    else {
                        lastPressTime = pressTime;
                        mClickTimer = new Timer();
                        mClickTimer.schedule(new Task(), 300);
                    }
                    if (saveScale == minScale) {
                        scaleMatrixToBounds();
                    }
                }

                break;

            case TouchEvent.OTHER_POINT_UP:
                mode = NONE;
                velocity = 0;
                savedMatrix.setMatrix(matrix);
                oldDist = spacing(event);
                //Log.d(TAG, "mode=NONE");
                break;

            case TouchEvent.POINT_MOVE:
                allowInert = false;
                if (mode == DRAG) {
                    float deltaX = curr.getPointX() - last.getPointX();
                    float deltaY = curr.getPointY() - last.getPointY();

                    long dragTime = System.currentTimeMillis();

                    velocity = (float) distanceBetween(curr, last) / (dragTime - lastDragTime) * FRICTION;
                    lastDragTime = dragTime;

                    checkAndSetTranslate(deltaX, deltaY);
                    // lastDelta.set(deltaX, deltaY);
                    lastDelta.modify(deltaX, deltaY);
                    //  last.set(curr.x, curr.y);
                    last.modify(curr.getPointX(), curr.getPointY());
                } else if (mScaleDetector == null && mode == ZOOM) {
                    float newDist = spacing(event);
                    if (rawEvent.getPointerCount() < 2) { break; }
                    //There is one serious trouble: when you scaling with two fingers, then pick up first finger of gesture, ACTION_MOVE being called.
                    //Magic number 50 for this case
                   // if (10 > Math.abs(oldDist - newDist) || Math.abs(oldDist - newDist) > 50) break;
                    float mScaleFactor = newDist / oldDist;
                    oldDist = newDist;

                    float origScale = saveScale;
                    saveScale *= mScaleFactor;
                    if (saveScale > maxScale) {
                        saveScale = maxScale;
                        mScaleFactor = maxScale / origScale;
                    } else if (saveScale < minScale) {
                        saveScale = minScale;
                       mScaleFactor = minScale / origScale;
                    }

                    calcPadding();
                    if (origWidth * saveScale <= width || origHeight * saveScale <= height) {
                        matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2);
                        if (mScaleFactor < 1) {
                            fillMatrixXY();
                            if (mScaleFactor < 1) {
                                scaleMatrixToBounds();
                            }
                        }
                    } else {
                        Point mid = midPointF(event);
                        matrix.postScale(mScaleFactor, mScaleFactor, mid.getPointX(), mid.getPointY());
                        fillMatrixXY();
                        if (mScaleFactor < 1) {
                            if (matrixX < -right)
                            { matrix.postTranslate(-(matrixX + right), 0); }
                            else if (matrixX > 0)
                            { matrix.postTranslate(-matrixX, 0); }
                            if (matrixY < -bottom)
                            { matrix.postTranslate(0, -(matrixY + bottom)); }
                            else if (matrixY > 0)
                            { matrix.postTranslate(0, -matrixY); }
                        }
                    }
                    checkSiding();
                }
                break;
        }

        //setImageMatrix(matrix);
invalidate();
        return true;
    }

    @Override
    public void onDoubleClick(Component component) {
        if (saveScale == 1) {
            final float targetScale = maxScale / saveScale;
            matrix.postScale(targetScale, targetScale, start.getPointX(), start.getPointY());
            saveScale = maxScale;
        } else {
            matrix.postScale(minScale / saveScale, minScale / saveScale, width / 2, height / 2);
            saveScale = minScale;
        }
        calcPadding();
        checkAndSetTranslate(0, 0);
        invalidate();
    }


    private class Task extends TimerTask {
        public void run() {
            // mTimerHandler.sendEmptyMessage(0);
            mTimerHandler.sendEvent(InnerEvent.get());
        }
    }


    private class ScaleListenner implements ScaledListener {

        @Override
        public void onScaleStart(Component component, ScaleInfo scaleInfo) {
            mode = ZOOM;

        }

        @Override
        public void onScaleUpdate(Component component, ScaleInfo scaleInfo) {
            HiLog.info(hiLogLabel, saveScale + "onScaleUpdate");
//            System.out.print(saveScale + "onScaleUpdate");
            float mScaleFactor = (float) Math.min(Math.max(.95f, scaleInfo.scale), 1.05);
            float origScale = saveScale;
            saveScale *= mScaleFactor;
            if (saveScale > maxScale) {
                saveScale = maxScale;
                mScaleFactor = maxScale / origScale;
            } else if (saveScale < minScale) {
                saveScale = minScale;
                mScaleFactor = minScale / origScale;
            }
            right = width * saveScale - width - (2 * redundantXSpace * saveScale);
            bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
            if (origWidth * saveScale <= width || origHeight * saveScale <= height) {
                matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2);
                if (mScaleFactor < 1) {
                    // matrix.getValues(m);
                    /*float x = m[Matrix.MTRANS_X];
                    float y = m[Matrix.MTRANS_Y];*/
                    float x = matrix.getTranslateX();
                    float y = matrix.getTranslateY();
                    if (mScaleFactor < 1) {
                        if (Math.round(origWidth * saveScale) < width) {
                            if (y < -bottom)
                            { matrix.postTranslate(0, -(y + bottom)); }
                            else if (y > 0)
                            { matrix.postTranslate(0, -y); }
                        } else {
                            if (x < -right)
                            { matrix.postTranslate(-(x + right), 0); }
                            else if (x > 0)
                            { matrix.postTranslate(-x, 0); }
                        }
                    }
                }
            } else {
                // matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());

                matrix.postScale(mScaleFactor, mScaleFactor, scaleInfo.startPoint.getPointX(), scaleInfo.startPoint.getPointY());
           /*     matrix.getValues(m);
                float x = m[Matrix.MTRANS_X];
                float y = m[Matrix.MTRANS_Y];*/
                float x = matrix.getTranslateX();
                float y = matrix.getTranslateY();
                if (mScaleFactor < 1) {
                    if (x < -right)
                    { matrix.postTranslate(-(x + right), 0); }
                    else if (x > 0)
                    { matrix.postTranslate(-x, 0); }
                    if (y < -bottom)
                    { matrix.postTranslate(0, -(y + bottom)); }
                    else if (y > 0)
                    { matrix.postTranslate(0, -y); }
                }


            }
            HiLog.info(hiLogLabel, saveScale + "onScaleUpdate");
//            System.out.print(saveScale + "onScaleUpdate");
            invalidate();
        }

        @Override
        public void onScaleEnd(Component component, ScaleInfo scaleInfo) {

        }
    }

    /*	static class TimeHandler extends Handler {
            private final WeakReference<TouchImageView> mService;

            TimeHandler(TouchImageView view) {
                mService = new WeakReference<TouchImageView>(view);

            }
            @Override
            public void handleMessage(Message msg)
            {
                mService.get().performClick();
                if (mService.get().mOnClickListener != null) mService.get().mOnClickListener.onClick(mService.get());
            }
        }*/
    static class TimeHandler extends EventHandler {
        private final WeakReference<TouchImageView> mService;

        TimeHandler(TouchImageView view, EventRunner eventRunner) {
            super(eventRunner);
            mService = new WeakReference<TouchImageView>(view);

        }

        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            // mService.get().performClick();
            mService.get().callOnClick();
            if (mService.get().mOnClickListener != null) { mService.get().mOnClickListener.onClick(mService.get()); }
        }
    }
};